#include "config.h"
#include "bencoding.h"
#include <cctype>
#include <memory>
#include <algorithm>

namespace tiny_bt
{
p_int b_value::to_int()
{
	return std::dynamic_pointer_cast<b_int>(shared_from_this());
}

p_list b_value::to_list()
{
	return std::dynamic_pointer_cast<b_list>(shared_from_this());
}

p_dic b_value::to_dic()
{
	return std::dynamic_pointer_cast<b_dic>(shared_from_this());
}

p_str b_value::to_str()
{
	return std::dynamic_pointer_cast<b_str>(shared_from_this());
}

p_value b_reader::parse(std::istream & i)
{
	int c;
	if ((c = i.peek()) != EOF)
	{
		if (std::isdigit(c))
		{
			return extract_str(i);
		}
		else if (c == 'i')
		{
			return extract_int(i);
		}
		else if (c == 'l')
		{
			return extract_list(i);
		}
		else if(c == 'd')
		{
			return extract_dic(i);
		}
		else
		{
			throw bd_invalid_doc_exception();
		}
	}
	throw bd_invalid_doc_exception();
}

p_int b_reader::extract_int(std::istream & i)
{
	char c;
	if (!i.get(c) || c != 'i')
	{
		throw bd_invalid_doc_exception();
	}

	int v;
	i >> v;

	if (!i.get(c) || c != 'e') { throw bd_invalid_doc_exception();
	}

	return std::make_shared<b_int>(v);

}

p_str b_reader::extract_str(std::istream & i)
{
	int l;
	i >> l;
	char c;
	if (!i.get(c) || c != ':')
	{
		throw bd_invalid_doc_exception();
	}

	b_str::value_type buff(l, 0);
	if (!i.read(&buff[0], l))
	{
		throw bd_invalid_doc_exception();
	}

	return std::make_shared<b_str>(move(buff));
}

p_dic b_reader::extract_dic(std::istream & i)
{
	char c;
	if (!i.get(c) || c != 'd')
	{
		throw bd_invalid_doc_exception();
	}

	auto dic = std::make_shared<b_dic>();

	do
	{
		auto k(extract_str(i));
		auto v(parse(i));
		dic->set_key_value(k, v);

		auto p = i.peek();
		if (p == EOF)
		{
			throw bd_invalid_doc_exception();
		}
		c = static_cast<char>(p);
	} while (c != 'e');

	i.get(c);
	return dic;
}

p_list b_reader::extract_list(std::istream & i)
{
	char c;
	if (!i.get(c) || c != 'l')
	{
		throw bd_invalid_doc_exception();
	}

	auto list = std::make_shared<b_list>();

	do
	{
		auto v(parse(i));
		list->attatch(v);

		auto p = i.peek();
		if (p == EOF)
		{
			throw bd_invalid_doc_exception();
		}
		c = static_cast<char>(p);
	} while (c != 'e');

	i.get(c);
	return list;
}

std::ostream & b_int::write(std::ostream & o)
{
	o.put('i');
	o << this->value;
	o.put('e');
	return o;
}

std::ostream & b_str::write(std::ostream & o)
{
	o << this->value.size();
	o.put(':');
	o.write(&value[0], value.size());
	return o;
}

std::ostream & b_list::write(std::ostream & o)
{
	o.put('l');
	for (const auto& l : this->value)
	{
		l->write(o);
	}
	o.put('e');
	return o;
}

std::ostream & b_dic::write(std::ostream & o)
{
	std::sort(value.begin(),
		value.end(),
		[](const node& l, const node& r) {return b_str::alphabetical_less()(*l.first, *r.first); });
	o.put('d');
	for (const auto& l : this->value)
	{
		l.first->write(o);
		l.second->write(o);
	}
	o.put('e');
	return o;
}

void b_writer::write(p_value b, std::ostream & o)
{
	b->write(o);
}

}

